﻿using System.Globalization;
using System.Linq;

namespace SKQMSLib
{    
    public abstract class EquationBase
    {
        protected Expression leftExpression;
        protected Expression rightExpression;
        public string EquationString
        {
            get;
            protected set;
        }        
        public string SolutionString
        {
            get;
            protected set;
        }             
        public string UnknownIDString
        {
            get;
            protected set;
        }

        public string GetSolution(bool digitGrouping) 
        {
            if (digitGrouping) return UnknownIDString + "=" + GroupDigits();
            return UnknownIDString + "=" + SolutionString;
        }

        protected string GroupDigits()
        {
            string initialIntegerString = "", tempString = "", finalIntegerString = "", fractionalString = "";
            int i, j;
            
            if (SolutionString.Contains('.'))
            {
                //get the integer part (no need to reverse it after)
                for (i = SolutionString.IndexOf('.') - 1; i >= 0; i--) initialIntegerString += SolutionString[i];

                //get the fractional part
                fractionalString = ".";
                for (i = SolutionString.IndexOf('.') + 1; i < SolutionString.Length; i++) 
                    fractionalString += SolutionString[i];
            }
            else 
            {
                //reverse the integer part
                for (i = SolutionString.Length - 1; i >= 0; i--)
                {
                    initialIntegerString += SolutionString[i];
                }
            }

            //insert grouping element once every 3 characters and copy to tempString
            for (i = 0, j = 0; i < initialIntegerString.Length; i++, j++)
            {
                if (j == 3) 
                {
                    tempString = tempString + ","; 
                    j = 0; 
                }
                tempString += initialIntegerString[i];
            }

            //if the number is negative, remove extra grouping element
            if (tempString[tempString.Length - 1] == '-' && tempString[tempString.Length - 2] == ',') 
                tempString = tempString.Remove(tempString.Length - 2, 1); 
            
            //reverse tempString and copy to finalIntegerString
            initialIntegerString = "";
            for (i = tempString.Length - 1; i >= 0; i--)
            {
                finalIntegerString += tempString[i];
            }

            return finalIntegerString + fractionalString;
        }
        
        protected virtual void Solve() { }
    }

    public class LinearAlgebraicEquation : EquationBase
    {
        public LinearAlgebraicEquation(string argString)
        {
            string leftExpString = "", rightExpString = "";
            int i;

            //search for missing operators (if a letter is next to a digit)
            for (i = 0; i < argString.Length; i++)
            {
                //for example: x2, 2x
                if (CheckIf.IsLetter(argString[i]))
                {
                    if (i > 0 && CheckIf.IsNumber(argString[i - 1]))
                        throw new UnkownCharacterException("A sign is missing !");
                    if ((i < argString.Length - 1) && CheckIf.IsNumber(argString[i + 1]))
                        throw new UnkownCharacterException("A sign is missing !");
                }
            }
            //check for invalid sign exceptions
            if (argString[0] == '=') throw new InvalidSignPositionException("Equal sign at the beginning of the string !");
            if (argString[argString.Length - 1] == '=') throw new InvalidSignPositionException("Equal sign at the end of the equation !");
            if (argString.Contains('=') == false) throw new InvalidSignPositionException("No equal sign !");
            if (argString.IndexOf('=') != argString.LastIndexOf('=')) throw new InvalidSignPositionException("More than one equal sign !");
            else
            {
                //get left expression
                for (i = 0; i < argString.IndexOf('='); i++) leftExpString += argString[i];
                //get right expression
                for (i = argString.IndexOf('=') + 1; i < argString.Length; i++) rightExpString += argString[i];
            }

            leftExpression = new Expression(leftExpString);
            rightExpression = new Expression(rightExpString);

            //find the identifier of the unknown
            if (leftExpression.HasUnknown) UnknownIDString = leftExpression.result.id;
            if (rightExpression.HasUnknown) UnknownIDString = rightExpression.result.id;

            EquationString = argString;
            Solve();
        }

        protected override void Solve()
        {
            double result = 0;
            if (leftExpression.HasUnknown == false && rightExpression.HasUnknown == false) throw new UnknownRelatedException("No unknown !");
            if (leftExpression.HasUnknown == true && rightExpression.HasUnknown == true) throw new UnknownRelatedException("Only one instance of an unknown is allowed !");

            if (leftExpression.HasUnknown)
            {
                //set initial value of result to the other expression's value
                result = rightExpression.result.value;
                //cycle through the operation list of the unknown
                //and apply inverse operation on result
                for (int i = leftExpression.result.unkOps.Count - 1; i >= 0; i--)
                {
                    double value = leftExpression.result.unkOps[i].value;

                    switch (leftExpression.result.unkOps[i].operation)
                    {
                        case '/':
                            result = 1 / (result * (1 / value));
                            break;

                        case '*':
                            result = result * (1 / value);
                            break;
                        
                        case '+':
                            result = result - value;
                            break;

                        case '-':
                            result = result + value;
                            break;
                    }
                }
            }
            if (rightExpression.HasUnknown)
            {
                //set initial value of result to the other expression's value
                result = leftExpression.result.value;
                //cycle through the operation list of the unknown
                //and apply inverse operation on result
                for (int i = rightExpression.result.unkOps.Count - 1; i >= 0; i--)
                {
                    double value = rightExpression.result.unkOps[i].value;

                    switch (rightExpression.result.unkOps[i].operation)
                    {
                        case '/':
                            result = 1 / (result * (1 / value));
                            break;

                        case '*':
                            result = result * (1 / value);
                            break;

                        case '+':
                            result = result - value;
                            break;

                        case '-':
                            result = result + value;
                            break;
                    }
                }
            }
            if (CheckIf.IsOutOfBounds(result)) throw new NumberOutOfBoundsException("Value is out of bounds !");
            SolutionString = result.ToString(CultureInfo.InvariantCulture.NumberFormat);
        }
    }
}